//
//  FLForceDirectedLayout.m
//  SpringGraph Test
//
//  Created by Jack Herrington on 5/30/10.
//  Copyright 2010 Myself. All rights reserved.
//

#import "FLForceDirectedLayout.h"

@interface FLRefresher : NSObject <FLIterator> {
	FLForceDirectedLayout *layout;
}
- (FLRefresher *)initWithLayout:(FLForceDirectedLayout *)_layout;
- (void)forEachNode:(FLNode *)node itemId:(NSInteger)itemId;
@end

@implementation FLRefresher
- (FLRefresher *)initWithLayout:(FLForceDirectedLayout *)_layout {
	self = [super init];
	if ( self ) {
		layout = [_layout retain];
	}
	return self;
}
- (void)dealloc {
	[layout release];
	[super dealloc];
}

- (void)forEachNode:(FLNode *)node itemId:(NSInteger)itemId {
	CGPoint pt = [layout.provider getPosition:itemId];
	node.x = pt.x;
	node.y = pt.y;
	node.repulsion =[layout.provider getRepulsion:itemId];
}
@end

@interface FLCommitter : NSObject <FLIterator> {
	FLForceDirectedLayout *layout;
}
- (FLCommitter *)initWithLayout:(FLForceDirectedLayout *)_layout;
- (void)forEachNode:(FLNode *)node itemId:(NSInteger)itemId;
@end

@implementation FLCommitter
- (FLCommitter *)initWithLayout:(FLForceDirectedLayout *)_layout {
	self = [super init];
	if ( self ) {
		layout = [_layout retain];
	}
	return self;
}
- (void)dealloc {
	[layout release];
	[super dealloc];
}

- (void)forEachNode:(FLNode *)node itemId:(NSInteger)itemId {
	[layout.provider setPosition:itemId position:CGPointMake(node.x,node.y)];
}
@end

@interface FLForceDirectedLayout (PrivateMethods)

- (void)forEachNode:(FLNode *)node itemId:(NSInteger)itemId;
- (void)forEachEdge:(id<NSObject, FLEdge>)edge;
- (void)forEachNodePair:(FLNode *)a b:(FLNode *)b;

- (void)setup;
- (void)relaxEdges;
- (void)avoidLabels;
- (void)moveNodes;
- (void)relax;

- (void)startDamper;
- (void)stopDamper;
- (void)stopMotion;
- (void)damp;

@end


@implementation FLForceDirectedLayout

@synthesize motionLimit;

- (void)setRigidity:(float)r {
	_newRigidity = r;
}

- (float)rigidity {
	return _rigidity;
}

- (void)setDragNode:(FLNode *)n {
	[_dragNode release];
	_dragNode = n;
}

- (FLNode *)dragNode {
	return _dragNode;
}

- (void)setProvider:(id<NSObject, FLDataProvider>)p {
	_provider = p;
}

- (id<FLDataProvider>) provider {
	return _provider;
}

- (void)setup {
	_damping = true;
	_damper = 0.0;
	_maxMotion = 0;
	_lastMaxMotion = 0;
	_motionRatio = 0;
	_rigidity = 0.25;
	_newRigidity = 0.25;
	self.motionLimit = 0.01;
	_newMaxMotion = 0;
}

- (FLForceDirectedLayout *)initWithProvider:(id<NSObject, FLDataProvider>)p {
	self = [super init];
	if ( self ) {
		[self setup];
		_provider = [p retain];
	}
	return self;
}

- (FLForceDirectedLayout *)init {
	self = [super init];
	if ( self ) {
		[self setup];
	}
	return self;
}

- (void)dealloc {
	_provider = nil;
	[_dragNode release];
	[super dealloc];
}

- (void)relaxEdges {
	[_provider forAllEdges:self];
}

- (void)avoidLabels {
	[_provider forAllNodePairs:self];
}

- (void)startDamper {
	_damping = true;
}

- (void)stopDamper {
	_damping = false;
	_damper = 1.0;
}

- (void)reset {
	_damping = true;
	_damper = 1.0;
}

- (void)stopMotion {
	_damping = true;
	if ( _damper > 0.3 ) 
		_damper = 0.3;
	else
		_damper = 0;
}

- (void)damp {
	if (_damping) {
		if(_motionRatio<=0.001) {
			if ((_maxMotion<0.2 || (_maxMotion>1 && _damper<0.9)) && _damper > 0.01) _damper -= 0.01;
			else if (_maxMotion<0.4 && _damper > 0.003) _damper -= 0.003;
			else if(_damper>0.0001) _damper -=0.0001;
		}
	}
	if( _maxMotion < self.motionLimit && _damping ) {
		_damper = 0;
	}
}

- (void)moveNodes {
	_lastMaxMotion = _maxMotion;
	_newMaxMotion = 0;
	
	[_provider forAllNodes:self];
	
	_maxMotion = _newMaxMotion;

	if (_maxMotion>0) _motionRatio = _lastMaxMotion/_maxMotion-1;
	else _motionRatio = 0; 
	
	[self damp];
}

- (void)relax {
	[_provider forAllNodes:[[[FLRefresher alloc] initWithLayout:self] autorelease]];

	for( int i = 0; i < 5; i++ ) {
		[self relaxEdges];
		[self avoidLabels];
		[self moveNodes];
	}
	
	if ( _rigidity != _newRigidity) _rigidity = _newRigidity;
	
	[_provider forAllNodes:[[[FLCommitter alloc] initWithLayout:self] autorelease]];
}

- (bool)tick {
	if (!(_damper<0.1 && _damping && _maxMotion<self.motionLimit)) {
		[self relax];
		return true;
	} else {
		return false;
	}
}

float max( float a, float b ) {
	return ( a > b ) ? a : b;
}

float min( float a, float b ) {
	return ( a < b ) ? a : b;
}

- (void)forEachNode:(FLNode *)n itemId:(NSInteger)itemId {
	float dx = n.dx;
	float dy = n.dy;
	dx *= _damper;
	dy *= _damper;
	
	n.dx = dx/2;
	n.dy = dy/2;
	
	float distMoved = sqrt(dx*dx+dy*dy);
	
	if (!(n==_dragNode) ) {
		n.x = n.x + max(-30, min(30, dx));
		n.y = n.y + max(-30, min(30, dy));
	}
	
	_newMaxMotion = max( distMoved, _newMaxMotion );
}

- (void)forEachEdge:(id<NSObject, FLEdge>)e {
	float vx = e.to.x - e.from.x;
	float vy = e.to.y - e.from.y;
	float len = sqrt(vx * vx + vy * vy);
	
	float edgeLength = [self.provider getEdgeLength:e.from to:e.to];
	
	float dx = vx * _rigidity;
	if(isnan(dx)) {
		dx = dx;
	}
	float dy = vy * _rigidity;
	if(isnan(dy)) {
		dy = dy;
	}
	
	dx /=(edgeLength*100);
	if(isnan(dx)) {
		dx = dx;
	}
	
	int length = [self.provider getEdgeLength:e.from to:e.to];
	int div = length * 100;
	float ddy = dy;
	dy = dy / div;
	ddy /=(edgeLength*100);
	if(isnan(dy)) {
		dy = dy;
	}
	e.to.dx = e.to.dx - dx*len;
	e.to.dy = e.to.dy - dy*len;
	e.from.dx = e.from.dx + dx*len;
	e.from.dy = e.from.dy + dy*len;
}

- (void)forEachNodePair:(FLNode *)n1 b:(FLNode *)n2 {
	float dx = 0;
	float dy = 0;
	float vx = n1.x - n2.x;
	float vy = n1.y - n2.y;
	float len = vx * vx + vy * vy;
	
	if (len == 0) {
		dx = (float)random()/RAND_MAX;
		dy = (float)random()/RAND_MAX;
	} else if (len < 360000) {
		dx = vx / len;
		dy = vy / len;
	}
	
	float repSum = n1.repulsion * n2.repulsion / 100;
	float factor = repSum * _rigidity;
	
	n1.dx += dx*factor;
	n1.dy += dy*factor;
	n2.dx -= dx*factor;
	n2.dy -= dy*factor;
}

@end
